package showtime.framework.net.tcp
{
	/**
	 * BaseSocket.as
	 * Yin Hao
	 * @version 1.0.0
	 * 
	 * Description.
	 * 
	 * Copyright (c) 2012 Yin Hao
	 * 
	 * Permission is hereby granted, free of charge, to any person obtaining a copy
	 * of this software and associated documentation files (the "Software"), to deal
	 * in the Software without restriction, including without limitation the rights
	 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	 * copies of the Software, and to permit persons to whom the Software is
	 * furnished to do so, subject to the following conditions:
	 * 
	 * The above copyright notice and this permission notice shall be included in
	 * all copies or substantial portions of the Software.
	 * 
	 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	 * THE SOFTWARE.
	 */
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.ObjectEncoding;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import showtime.framework.events.SocketEvent;
	import showtime.framework.net.HeartBeatChecker;
	import showtime.framework.net.IHeartBeatChecker;

	public class BaseSocket extends EventDispatcher
	{
		/**
		 * Socket instance. 
		 */		
		private var socket:Socket = null;
		/**
		 * Socket heart beat checker. 
		 */		
		private var heartBeatChecker:IHeartBeatChecker = null;
		
		private var isNewPacket:Boolean = true;

		private var packetLength:uint = 0;
	
		private var inStream:ByteArray = null;
		
		public var callback:Function = null;
		
		private static const PACKET_READ_SCOPE:int = 4;
	
		public function BaseSocket(objectEncoding:int = ObjectEncoding.AMF3, endian:String = Endian.BIG_ENDIAN)
		{  
			socket = new Socket();
			
			socket.objectEncoding = objectEncoding;
			
			socket.endian = endian;
			
			inStream = new ByteArray();
			
			initialize();
		}
		
		/**
		 * Initialize socket,such as register event listeners. 
		 * 
		 */		
		private function initialize():void
		{
			socket.addEventListener(Event.CONNECT, eventHandler);
			
			socket.addEventListener(Event.CLOSE, eventHandler);
			
			socket.addEventListener(IOErrorEvent.IO_ERROR, eventHandler);
			
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, eventHandler);
			
			socket.addEventListener(ProgressEvent.SOCKET_DATA, receivedHandler);
		}
		
		public function connected():Boolean
		{
			return socket.connected;
		}
		
		/**
		 * This function try to connect remote server.
		 *  
		 * @param host remote server host address.
		 * @param port remote server port.
		 * @param heartBeatCheck is start Heart Beat Checker.
		 * 
		 */		
		public function connect(host:String, port:int, heartBeatCheck:Boolean = false, keepAliveTimeout:int = 0, checkInterval:int = 0):BaseSocket
		{
			socket.connect(host, port);
		
			if (heartBeatCheck)
			{
				heartBeatChecker = new HeartBeatChecker(this, keepAliveTimeout, checkInterval);
			}
			
			return this;
		}
		/**
		 * Close socket connection. 
		 * 
		 */		
		public function close():void
		{
			socket.close();
		}
		
		/**
		 * Send message to remote server. 
		 * @param message
		 * 
		 */		
		public function sendPacket(packet:ByteArray):void
		{
			if (!connected())
			{
				return;
			}
			
			socket.writeInt(packet.length);		
			socket.writeBytes(packet);
	
			socket.flush();
		}
		
		/**
		 * Received data handler. 
		 * 
		 */		
		private function receivedHandler(event:ProgressEvent = null):void
		{
			// Is first packet.
			if (isNewPacket)
			{
				inStream.clear();				
				
				while (socket.bytesAvailable >= PACKET_READ_SCOPE && isNewPacket)
				{					
					packetLength = socket.readUnsignedInt();
					
					isNewPacket = false;
				}
			}
			// Read socket data.
			if (!isNewPacket)
			{			
				// Is received enough byte data for last packet.
				if (socket.bytesAvailable >= packetLength)
				{					
					try
					{		
						socket.readBytes(inStream, inStream.length, packetLength);
					}
					catch(error:Error)
					{
						throw new Error("[Socket failed] - the data from the socket invalid");	
					}					
					
					isNewPacket = true;
					
					// Is do heart beat check.		
					if (heartBeatChecker != null)
					{
						heartBeatChecker.update();
					}	
					
					callback.call(this, inStream);
								
				}
				else
				{
					try
					{						
						packetLength -= socket.bytesAvailable;
						
						socket.readBytes(inStream, inStream.length, socket.bytesAvailable);
					
					}
					catch(error:Error)
					{
						throw new Error("[Socket failed] - the data from the socket invalid");	
					}
				}
			} 
			
			// Recursive read remaining data from socket.
			if (socket.bytesAvailable >= PACKET_READ_SCOPE)
			{				
				isNewPacket = true;
				
				receivedHandler();
			}
		}		
		
		private function eventHandler(event:Event):void
		{
			switch (event.type)
			{
				case Event.CLOSE:
				{
					dispatchEvent(new SocketEvent(SocketEvent.CLOSE));
					break;
				}
				case Event.CONNECT:
				{
					dispatchEvent(new SocketEvent(SocketEvent.CONNECT));
					
					// Start heart beat checker.
					if (heartBeatChecker != null)
					{
						heartBeatChecker.start();
					}				
					
					break;
				}
				case IOErrorEvent.IO_ERROR:
				{
					dispatchEvent(new SocketEvent(SocketEvent.IO_ERROR));
					break;
				}	
				case SecurityErrorEvent.SECURITY_ERROR:
				{
					dispatchEvent(new SocketEvent(SocketEvent.SECURITY_ERROR));
					break;
				}
				default:
				{
					break;
				}
			}
		}
	}
}